/*
 *  Copyright (c) M2TK Project. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package m2tk.assistant.app.ui.view;

import cn.hutool.core.util.StrUtil;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import m2tk.assistant.app.ui.AssistantApp;
import m2tk.assistant.app.ui.component.*;
import m2tk.assistant.app.ui.event.ShowStreamInfoPaneRequest;
import m2tk.assistant.app.ui.task.AsyncQueryTask;
import m2tk.assistant.common.InfoView;
import m2tk.assistant.common.M2TKDatabase;
import m2tk.assistant.common.ProgramObserver;
import m2tk.assistant.common.StreamObserver;
import m2tk.assistant.common.domain.*;
import m2tk.assistant.common.event.RefreshInfoViewRequest;
import m2tk.assistant.common.presets.StreamTypes;
import m2tk.assistant.common.ui.RoundPanel;
import net.miginfocom.swing.MigLayout;
import org.jdesktop.application.Application;
import org.kordamp.ikonli.fluentui.FluentUiRegularAL;
import org.kordamp.ikonli.swing.FontIcon;
import org.pf4j.Extension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.awt.CardLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static java.util.stream.Collectors.toMap;
import static m2tk.assistant.common.ui.UIConstants.ICON_SIZE_L;
import static m2tk.assistant.common.ui.UIConstants.LIGHT_BLUE;

@Extension(ordinal = 1)
public class StreamInfoView extends JPanel
    implements InfoView, StreamObserver, ProgramObserver
{
    private static final Logger log = LoggerFactory.getLogger(StreamInfoView.class);

    public static final String TRANSPORT_STREAM_SUMMARY_PANE = "transport-stream-summary-pane";
    public static final String ELEMENTARY_STREAM_SUMMARY_PANE = "elementary-stream-summary-pane";
    public static final String MPEG_PROGRAM_INFO_PANE = "mpeg-program-info-pane";
    public static final String CA_SYSTEM_INFO_PANE = "ca-system-info-pane";
    public static final String NETWORK_INFO_PANE = "network-info-pane";
    public static final String DVB_SERVICE_INFO_PANE = "dvb-service-info-pane";

    private StreamInfoNaviPane naviPane;
    private TSSummaryPane tsSummaryPane;
    private ESSummaryPane esSummaryPane;
    private ProgramInfoPane programInfoPane;
    private CASystemInfoPane casInfoPane;
    private NetworkInfoPane networkInfoPane;
    private ServiceInfoPane serviceInfoPane;

    private JPanel cardPane;
    private CardLayout cardLayout;

    private List<StreamObserver> streamObservers;
    private List<ProgramObserver> programObservers;
    private StreamSource currentSource;

    private Application application;
    private M2TKDatabase database;
    private EventBus bus;

    private final String viewTitle = "传输流信息";
    private final Icon viewIcon = FontIcon.of(FluentUiRegularAL.GLANCE_24, ICON_SIZE_L, LIGHT_BLUE);
    private volatile long lastTimestamp;
    private final long MIN_QUERY_INTERVAL_MILLIS = 500;

    private static class StreamInfoSnapshot
    {
        private StreamSource source;
        private List<ElementaryStream> streams;
        private List<MPEGProgram> programs;
        private List<CASystemStream> caStreams;
        private List<SIMultiplex> tsActualNetwork;
        private List<SIMultiplex> tsOtherNetwork;
        private List<SIService> srvActualTS;
        private List<SIService> srvOtherTS;
        private OffsetDateTime networkTime;
    }

    public StreamInfoView()
    {
        initUI();
    }

    private void initUI()
    {
        cardLayout = new CardLayout();
        cardPane = new JPanel(cardLayout);

        naviPane = new StreamInfoNaviPane();
        tsSummaryPane = new TSSummaryPane();
        esSummaryPane = new ESSummaryPane();
        programInfoPane = new ProgramInfoPane();
        casInfoPane = new CASystemInfoPane();
        networkInfoPane = new NetworkInfoPane();
        serviceInfoPane = new ServiceInfoPane();

        cardPane.add("default", new RoundPanel());
        cardPane.add(TRANSPORT_STREAM_SUMMARY_PANE, tsSummaryPane);
        cardPane.add(ELEMENTARY_STREAM_SUMMARY_PANE, esSummaryPane);
        cardPane.add(MPEG_PROGRAM_INFO_PANE, programInfoPane);
        cardPane.add(CA_SYSTEM_INFO_PANE, casInfoPane);
        cardPane.add(NETWORK_INFO_PANE, networkInfoPane);
        cardPane.add(DVB_SERVICE_INFO_PANE, serviceInfoPane);

        setLayout(new MigLayout("insets 0", "[][grow]"));

        add(naviPane, "grow");
        add(cardPane, "grow, push");

        streamObservers = new ArrayList<>();
        esSummaryPane.setPopupListener(this::showStreamPopupMenu);

        programObservers = new ArrayList<>();
        programInfoPane.setPopupListener(this::showProgramPopupMenu);

        addComponentListener(new ComponentAdapter()
        {
            @Override
            public void componentShown(ComponentEvent e)
            {
                if (database != null)
                    queryStreamSnapshot();
            }
        });
    }

    @Override
    public void setupApplication(Application application)
    {
        this.application = application;
    }

    @Override
    public void setupDataSource(EventBus bus, M2TKDatabase database)
    {
        this.bus = bus;
        this.database = database;

        naviPane.setEventBus(bus);
        networkInfoPane.setEventBus(bus);
        bus.register(this);
    }

    @Override
    public JComponent getViewComponent()
    {
        return this;
    }

    @Override
    public String getViewTitle()
    {
        return viewTitle;
    }

    @Override
    public Icon getViewIcon()
    {
        return viewIcon;
    }

    @Override
    public List<JMenuItem> getContextMenuItem(MPEGProgram program)
    {
        if (program.isFreeAccess())
        {
            String text = (program.getName() == null)
                          ? String.format("播放 节目%d", program.getProgramNumber())
                          : String.format("播放 %s", program.getName());
            JMenuItem item = new JMenuItem();
            item.setText(text);
            item.addActionListener(e -> playProgram(program));
            return List.of(item);
        }
        return List.of();
    }

    @Override
    public List<JMenuItem> getContextMenuItem(ElementaryStream stream)
    {
        if (stream.isScrambled() ||
            !StrUtil.equalsAny(stream.getCategory(), StreamTypes.CATEGORY_VIDEO, StreamTypes.CATEGORY_AUDIO))
            return List.of();

        JMenuItem item = new JMenuItem();
        item.setText("播放 " + stream.getDescription());
        item.addActionListener(e -> playStream(stream));
        return List.of(item);
    }

    @Subscribe
    public void onRefreshInfoViewRequest(RefreshInfoViewRequest request)
    {
        long t1 = System.currentTimeMillis();
        if (t1 - lastTimestamp >= MIN_QUERY_INTERVAL_MILLIS && isShowing())
        {
            queryStreamSnapshot();
            lastTimestamp = System.currentTimeMillis();
        }
    }

    @Subscribe
    public void onShowStreamInfoPaneRequest(ShowStreamInfoPaneRequest request)
    {
        cardLayout.show(cardPane, request.pane());
    }

    public void setStreamObservers(List<StreamObserver> observers)
    {
        streamObservers.clear();
        streamObservers.addAll(observers);
    }

    public void setProgramObservers(List<ProgramObserver> observers)
    {
        programObservers.clear();
        programObservers.addAll(observers);
    }

    private void queryStreamSnapshot()
    {
        Supplier<StreamInfoSnapshot> query = () ->
        {
            StreamInfoSnapshot snapshot = new StreamInfoSnapshot();

            try
            {
                // 查询快照数据
                snapshot.source = database.getCurrentStreamSource();
                snapshot.streams = database.listElementaryStreams(true);
                snapshot.programs = database.listMPEGPrograms();
                snapshot.caStreams = database.listCASystemStreams();
                snapshot.tsActualNetwork = database.getActualNetworkMultiplexes();
                snapshot.tsOtherNetwork = database.getOtherNetworkMultiplexes();
                snapshot.srvActualTS = database.getActualTransportStreamServices();
                snapshot.srvOtherTS = database.getOtherTransportStreamServices();
                snapshot.networkTime = database.getLastTimestamp();

                Map<String, SIService> serviceMap = database.listRegularSIServices()
                                                            .stream()
                                                            .collect(toMap(service -> String.format("%d.%d",
                                                                                                    service.getTransportStreamId(),
                                                                                                    service.getServiceId()),
                                                                           service -> service));
                for (MPEGProgram program : snapshot.programs)
                {
                    String key = String.format("%d.%d", program.getTransportStreamId(), program.getProgramNumber());
                    String programName = Optional.ofNullable(serviceMap.get(key))
                                                 .map(SIService::getName)
                                                 .orElse(null);
                    program.setName(programName);
                }
                snapshot.programs.sort(Comparator.comparingInt(MPEGProgram::getProgramNumber));

                snapshot.source.setStreamCount(snapshot.streams.size());
                snapshot.source.setProgramCount(snapshot.programs.size());
            } catch (Exception ex)
            {
                log.warn("查询数据库异常：{}", ex.getMessage(), ex);
            }

            return snapshot;
        };

        Consumer<StreamInfoSnapshot> consumer = snapshot ->
        {
            esSummaryPane.updateElementaryStreamSummary(snapshot.streams);
            tsSummaryPane.updateSummary(snapshot.source, snapshot.streams, snapshot.networkTime);
            programInfoPane.updateProgramList(snapshot.programs);
            casInfoPane.updateStreamList(snapshot.caStreams);
            networkInfoPane.updateMultiplexes(snapshot.tsActualNetwork, snapshot.tsOtherNetwork);
            serviceInfoPane.updateServices(snapshot.srvActualTS, snapshot.srvOtherTS);

            currentSource = snapshot.source;

            naviPane.triggerFirstIfNoneSelected();
        };

        AsyncQueryTask<StreamInfoSnapshot> task = new AsyncQueryTask<>(application, query, consumer);
        task.execute();
    }

    private void playStream(ElementaryStream stream)
    {
        int videoPid = 0x1FFF;
        int audioPid = 0x1FFF;
        if (StrUtil.equals(stream.getCategory(), StreamTypes.CATEGORY_VIDEO))
            videoPid = stream.getStreamPid();

        if (StrUtil.equals(stream.getCategory(), StreamTypes.CATEGORY_AUDIO))
            audioPid = stream.getStreamPid();

        if (videoPid == 0x1FFF && audioPid == 0x1FFF)
        {
            String text = String.format("不支持的流类型：%s", stream.getDescription());
            JOptionPane.showMessageDialog(null, text);
            log.info(text);
            return;
        }

        log.info("播放'流{}'，类型：{}", stream.getStreamPid(), stream.getDescription());

        if (videoPid != 0x1FFF)
            AssistantApp.getInstance().playVideo(currentSource.getUri(), videoPid);
        else
            AssistantApp.getInstance().playAudio(currentSource.getUri(), audioPid);
    }

    private void playProgram(MPEGProgram program)
    {
        int videoPid = 0x1FFF;
        int audioPid = 0x1FFF;
        for (ElementaryStream es : program.getElementaryStreams())
        {
            if (es.isScrambled())
                continue;

            if (StreamTypes.CATEGORY_VIDEO.equals(es.getCategory()))
                videoPid = es.getStreamPid();

            if (StreamTypes.CATEGORY_AUDIO.equals(es.getCategory()))
                audioPid = es.getStreamPid();
        }

        String programName = (program.getName() == null)
                             ? "节目" + program.getProgramNumber()
                             : program.getName();

        if (videoPid == 0x1FFF && audioPid == 0x1FFF)
        {
            String text = program.isFreeAccess()
                          ? programName + "无可播放内容"
                          : programName + "完全加扰，无法播放";
            JOptionPane.showMessageDialog(null, text);
            log.info(text);
            return;
        }

        log.info("播放'{}'，视频PID：{}，音频PID：{}", programName, videoPid, audioPid);

        AssistantApp.getInstance().playProgram(currentSource.getUri(), program.getProgramNumber());
    }

    private void showStreamPopupMenu(MouseEvent event, ElementaryStream stream)
    {
        JPopupMenu popupMenu = new JPopupMenu();
        for (StreamObserver observer : streamObservers)
        {
            List<JMenuItem> menuItems = observer.getContextMenuItem(stream);
            for (JMenuItem menuItem : menuItems)
                popupMenu.add(menuItem);
        }
        popupMenu.show(event.getComponent(), event.getX(), event.getY());
    }

    private void showProgramPopupMenu(MouseEvent event, MPEGProgram program)
    {
        JPopupMenu popupMenu = new JPopupMenu();
        for (ProgramObserver observer : programObservers)
        {
            List<JMenuItem> menuItems = observer.getContextMenuItem(program);
            for (JMenuItem menuItem : menuItems)
                popupMenu.add(menuItem);
        }
        popupMenu.show(event.getComponent(), event.getX(), event.getY());
    }
}
